/*!
\copyright  Copyright (c) 2024 - 2025 Qualcomm Technologies International, Ltd.
            All Rights Reserved.
            Qualcomm Technologies International, Ltd. Confidential and Proprietary.
\file
\ingroup    kymera
\brief      Handles LE audio decoder chain graphs for mono, dual mono and stereo cis/bis usecases.
            Inputs of the decoder audio graph created by this module will be connected to the iso source endpoints.
*/

#if defined(INCLUDE_LE_AUDIO_UNICAST) || defined(INCLUDE_LE_AUDIO_BROADCAST)

#include "kymera_le_audio_decoder.h"
#include "kymera.h"
#include "kymera_data.h"
#include "kymera_le_common.h"
#include "kymera_volume.h"

#include "multidevice.h"
#include <logging.h>
#include <chain.h>

/*
    Parameters for LE voice for the initial testing.
*/
#define LC3_DECODE_SCO_ISO_PACKET_LEN   40
#define LC3_DECODE_SCO_ISO_BUFFER_SIZE  640
#define MIXER_GAIN_RAMP_SAMPLES         24000

#define DECODE_ONLY_LEFT   0
#define DECODE_ONLY_RIGHT  1

/*! Code assertion that can be checked at run time. This will cause a panic. */
#define assert(x)   PanicFalse(x)

static const appKymeraLeAudioChainTable *lea_decoder_chain_config_map = NULL;

static const chain_config_t* kymeraLeAudioDecoder_FindChainFromConfigTable(uint8 codec_type, appKymeraLeStreamType stream_type)
{
    uint8 index;
    appKymeraLeAudioDecoderConfigType decoder_config_type;
    bool is_stereo_device = Multidevice_IsDeviceStereo();

    assert(lea_decoder_chain_config_map);

    switch (stream_type)
    {
        default:
        case KYMERA_LE_STREAM_MONO:
            decoder_config_type = is_stereo_device ? KYMERA_LE_AUDIO_DECODER_CONFIG_TYPE_SINGLE_DECODER_TO_DUAL_MONO
                                                   : KYMERA_LE_AUDIO_DECODER_CONFIG_TYPE_SINGLE_DECODER_TO_MONO;
        break;

        case KYMERA_LE_STREAM_STEREO_USE_LEFT:
        case KYMERA_LE_STREAM_STEREO_USE_RIGHT:
            decoder_config_type = is_stereo_device ? KYMERA_LE_AUDIO_DECODER_CONFIG_TYPE_SINGLE_DECODER_TO_DUAL_MONO
                                                   : KYMERA_LE_AUDIO_DECODER_CONFIG_TYPE_SINGLE_DECODER_TO_MONO;
        break;

        case KYMERA_LE_STREAM_STEREO_USE_BOTH:
            decoder_config_type = KYMERA_LE_AUDIO_DECODER_CONFIG_TYPE_SINGLE_DECODER_TO_STEREO;
        break;

        case KYMERA_LE_STREAM_DUAL_MONO:
            decoder_config_type = is_stereo_device ? KYMERA_LE_AUDIO_DECODER_CONFIG_TYPE_DUAL_DECODER_TO_STEREO
                                                   : KYMERA_LE_AUDIO_DECODER_CONFIG_TYPE_SINGLE_DECODER_TO_MONO;
        break;

        case KYMERA_LE_STREAM_DUAL_MONO_USE_LEFT:
        case KYMERA_LE_STREAM_DUAL_MONO_USE_RIGHT:
            decoder_config_type = KYMERA_LE_AUDIO_DECODER_CONFIG_TYPE_DUAL_DECODER_TO_MONO;
        break;
    }

    DEBUG_LOG("kymeraLeAudio_FindChainFromConfigTable for codec_type: %d, decoder_config: %d", codec_type, decoder_config_type);
    for (index = 0; index < lea_decoder_chain_config_map->table_length; index++)
    {
        if (lea_decoder_chain_config_map->chain_table[index].codec_type == codec_type
            && lea_decoder_chain_config_map->chain_table[index].decoder_config_type == decoder_config_type
            )
        {
            return lea_decoder_chain_config_map->chain_table[index].chain_config;
        }
    }

    Panic();
    return NULL;
}

static bool KymeraLeAudioDecoder_IsMixerOperatorPresent(kymera_chain_handle_t chain_handle)
{
    return (INVALID_OPERATOR != ChainGetOperatorByRole((chain_handle), (OPR_LEFT_RIGHT_MIXER)));
}

static void kymeraLeVoice_ConfigureLc3Decoder(Operator op, const le_speaker_config_t *le_speaker_params)
{
    DEBUG_LOG(" kymeraLeVoice_ConfigureLc3Decoder enum:appKymeraLeStreamType:%d, blocks per sdu %d", le_speaker_params->stream_type, le_speaker_params->codec_frame_blocks_per_sdu);
	bool check_is_stereo = FALSE;

    OperatorsLc3DecoderScoIsoSetFrameDuration(op, le_speaker_params->frame_duration);
    OperatorsStandardSetBufferSize(op, LC3_DECODE_SCO_ISO_BUFFER_SIZE);

    OperatorsLc3DecoderScoIsoSetPacketLength(op, le_speaker_params->frame_length);
    OperatorsStandardSetSampleRate(op, le_speaker_params->sample_rate);
    OperatorsStandardSetTimeToPlayLatency(op, le_speaker_params->presentation_delay);

    if (le_speaker_params->codec_frame_blocks_per_sdu > 1)
    {
        OperatorsLc3DecoderScoIsoSetBlocksPerSdu(op, le_speaker_params->codec_frame_blocks_per_sdu);
    }

    switch (le_speaker_params->stream_type)
    {
        case KYMERA_LE_STREAM_MONO:
        case KYMERA_LE_STREAM_DUAL_MONO:
        case KYMERA_LE_STREAM_DUAL_MONO_USE_LEFT:
        case KYMERA_LE_STREAM_DUAL_MONO_USE_RIGHT:
            /* No action */
            break;

        case KYMERA_LE_STREAM_STEREO_USE_RIGHT:
            {
                /* In case of Stereo stream render only right data */
                OperatorsLc3DecoderScoIsoSetNumOfChannels(op, 2);
                OperatorsLc3DecoderScoIsoSetMonoDecode(op, DECODE_ONLY_RIGHT);
            }
            break;

        case KYMERA_LE_STREAM_STEREO_USE_BOTH:
            {
                check_is_stereo = Multidevice_IsDeviceStereo();
            }
                /* fall through */
        case KYMERA_LE_STREAM_STEREO_USE_LEFT:
            {
                OperatorsLc3DecoderScoIsoSetNumOfChannels(op, 2);

                /* In usecases, where mixer operator is absent, render only left data as in the case of SCO */
                if (!check_is_stereo && !KymeraLeAudioDecoder_IsMixerOperatorPresent(KymeraGetTaskData()->chain_le_decoder_handle))
                {
                        OperatorsLc3DecoderScoIsoSetMonoDecode(op, DECODE_ONLY_LEFT);
                }
            }
            break;

        default:
            break;
    }
}

static void kymeraLeAudioDecoder_ConfigureDecoder(kymera_chain_handle_t chain_handle,
                                                  const le_speaker_config_t *le_speaker_params)
{
    switch (le_speaker_params->codec_type)
    {
        case KYMERA_LE_AUDIO_CODEC_LC3:
        {
            Operator op = ChainGetOperatorByRole(chain_handle, OPR_LC3_DECODE_SCO_ISO);

            kymeraLeVoice_ConfigureLc3Decoder(op, le_speaker_params);

            if (le_speaker_params->iso_handle_right != LE_AUDIO_INVALID_ISO_HANDLE)
            {
                op = ChainGetOperatorByRole(chain_handle, OPR_LC3_DECODE_SCO_ISO_RIGHT);
                kymeraLeVoice_ConfigureLc3Decoder(op, le_speaker_params);
            }

            /* Configures mixer for usecases, such as stereo single cis mirroring/dual cis mirroring,
               where it's decoder graph contains mixer operator */
            KymeraLeAudioDecoder_ConfigureMixer(le_speaker_params->sample_rate, chain_handle);
        }
        break;

        case KYMERA_LE_AUDIO_CODEC_APTX_ADAPTIVE:
           /* TODO */
        break;

       default:
           break;

    }
}

#if defined(INCLUDE_CIS_MIRRORING) || defined(ENABLE_MULTI_PERIPHERAL_CIS)
void KymeraLeAudioDecoder_ConfigureMixer(uint32 sample_rate, kymera_chain_handle_t chain_handle)
{
    Operator mixer_op;

    if (GET_OP_FROM_CHAIN(mixer_op, chain_handle, OPR_LEFT_RIGHT_MIXER))
    {
        /* Select audio channel to render based on passed request */
        int gain_left, gain_right;

        gain_left = Multidevice_IsLeft() ? GAIN_FULL : GAIN_MIN;
        gain_right = Multidevice_IsLeft() ? GAIN_MIN : GAIN_FULL;

        OperatorsConfigureMixer(mixer_op, sample_rate, 1, gain_left, gain_right, GAIN_MIN, 1, 1, 0);
        OperatorsMixerSetNumberOfSamplesToRamp(mixer_op, MIXER_GAIN_RAMP_SAMPLES);
    }
}
#endif

void KymeraLeAudioDecoder_CreateChain(const le_speaker_config_t *le_speaker_params)
{
    const chain_config_t * config = NULL;
    kymeraTaskData *theKymera = KymeraGetTaskData();
    Sink from_air_ep_left;
    Source from_air_iso_source;

    config = kymeraLeAudioDecoder_FindChainFromConfigTable(le_speaker_params->codec_type, le_speaker_params->stream_type);

    theKymera->chain_le_decoder_handle = PanicNull(ChainCreate(config));

    /* Get sources and sinks for chain endpoints */
    from_air_ep_left = ChainGetInput(theKymera->chain_le_decoder_handle, EPR_ISO_FROM_AIR_LEFT);
    from_air_iso_source = StreamIsoSource(le_speaker_params->iso_handle);
    StreamConnect(from_air_iso_source, from_air_ep_left);

    if (le_speaker_params->iso_handle_right != LE_AUDIO_INVALID_ISO_HANDLE)
    {
        Sink from_air_ep_right = ChainGetInput(theKymera->chain_le_decoder_handle, EPR_ISO_FROM_AIR_RIGHT);
        Source from_air_iso_source_right = StreamIsoSource(le_speaker_params->iso_handle_right);
        StreamConnect(from_air_iso_source_right, from_air_ep_right);
    }
}

void KymeraLeAudioDecoder_ConfigureChain(const le_speaker_config_t *le_speaker_params)
{
    kymera_chain_handle_t chain = KymeraGetTaskData()->chain_le_decoder_handle;

    PanicNull(chain);

    kymeraLeAudioDecoder_ConfigureDecoder(chain, le_speaker_params);

    ChainConnect(chain);
}

void KymeraLeAudioDecoder_StartChain(void)
{
    kymeraTaskData *theKymera = KymeraGetTaskData();

    PanicNull(theKymera->chain_le_decoder_handle);

    ChainStart(theKymera->chain_le_decoder_handle);
}

void KymeraLeAudioDecoder_StopChain(void)
{
    kymeraTaskData *theKymera = KymeraGetTaskData();

    if(!theKymera->chain_le_decoder_handle)
    {
        DEBUG_LOG("KymeraLeAudioDecoder_StopChain, not stopping - already idle/uncreated");
        return;
    }

    ChainStop(theKymera->chain_le_decoder_handle);
    ChainDestroy(theKymera->chain_le_decoder_handle);
}

Source KymeraLeAudioDecoder_GetOutput(unsigned endpoint_role)
{
    kymeraTaskData *theKymera = KymeraGetTaskData();

    return ChainGetOutput(theKymera->chain_le_decoder_handle, endpoint_role);
}

void Kymera_SetLeAudioDecoderChainTable(const appKymeraLeAudioChainTable *chain_table)
{
    lea_decoder_chain_config_map = chain_table;
}

#endif
